/**
 * 210824：升级修改【three.js r120-r131】
  * 20220304:[require] to [import]...jordia
 */
import {Vector3,BufferGeometry,BufferAttribute} from 'three';
import Utils from '../general/utils';
import GeometryUtils from '../general/geometry-utils';

/**
 * 边的对比函数
 * @param a
 * @param b
 * @returns {*}
 */
const lineCompare=(a,b)=>{
    return (a.start.equals(b.start)&&a.end.equals(b.end))||(a.start.equals(b.end)&&a.end.equals(b.start));
};

/**
 * 向量的对比函数
 * @param a
 * @param b
 * @returns {*}
 */
const vecCompare=(a,b)=>{
    return a.equals(b);
};

/**
 * 索引面的对比函数
 * @param a
 * @param b
 * @returns {boolean}
 */
const faceCompare=(a,b)=>{
    return (a.a===b.a&&a.b===b.b&&a.c===b.c)||
        (a.a===b.a&&a.b===b.c&&a.c===b.b)||
        (a.a===b.b&&a.b===b.c&&a.c===b.a)||
        (a.a===b.b&&a.b===b.a&&a.c===b.c)||
        (a.a===b.c&&a.b===b.a&&a.c===b.b)||
        (a.a===b.c&&a.b===b.b&&a.c===b.a);
};

/**
 * 边
 */
class Line{
    constructor(start,end){
        this.start=start?start.clone():new Vector3();
        this.end=end?end.clone():new Vector3();
    }
    get length(){
        return this.start.distanceToSquared(this.end);
    }
}

/**
 * 圆
 */
class Circle{
    constructor(center,radius,normal){
        this.center=center?center.clone():new Vector3();
        this.radius=radius||1e-6;
        this.normal=normal?normal.clone():new Vector3();
    }
    clone(){
        return new Circle(this.center,this.radius,this.normal);
    }
}

/**
 * 剖分边
 */
class TINLine{
    constructor(line,projectNormal){
        this.line=line;
        this.projectNormal=projectNormal;
        this.projectLine=new Line();
        GeometryUtils.computeProjectLineOnPlane(this.line.start,this.line.end,this.projectLine.start,this.projectLine.end,this.projectNormal,0);
        this.start=this.line.start;
        this.end=this.line.end;
    }
}

/**
 * 索引面
 */
class TINFace {
    constructor(aIndex, bIndex, cIndex) {
        this.a = aIndex;
        this.b = bIndex;
        this.c = cIndex;
    }
}

/**
 * 使用边界点进行剖分（一组外边界点，N组一阶孔洞边界）
 */
class TriangulateWithOutlinePoints {
    constructor() {}

    /**
     * 输入外圈边界点及孔洞边界点组进行剖分，返回顶点及索引面集（内部方法）
     * @param outerPoints 外边界
     * @param innerPointsGroup 孔洞
     * @returns {{averageNormal: *, faces: *, points: *}}
     * @constructor
     */
    static TINOutlines(outerPoints, innerPointsGroup) {
        if (outerPoints.length < 3) {
            console.warn('outline points of JORScripts.Triangulation cannot be less than 3!');
            return;
        }
        let _outerPoints = Array.from(outerPoints);
        let _averageNormal =new Vector3();
        GeometryUtils.computeAverageNormalOfPointSequence(_outerPoints,_averageNormal);
        let _innerPointsGroup = Array.from(innerPointsGroup);
        let info = TriangulateWithOutlinePoints.createTINLines(_outerPoints, _innerPointsGroup,_averageNormal);
        let TINFaces = TriangulateWithOutlinePoints.TINOuter(info.allPoints, info.allLines, info.outerLines, info.innerLinesGroup,_averageNormal);
        return {points: info.allPoints, faces: TINFaces,averageNormal:_averageNormal};
    }

    /**
     * 检测边是否与边集相交（内部方法）
     * @param line
     * @param lines
     * @returns {boolean}
     */
    static isIntersectWithAnyLine(line, lines) {
        for (let i of lines) {
            if (GeometryUtils.ifTwoLinesIntersect3D(line.projectLine.start, line.projectLine.end, i.projectLine.start, i.projectLine.end)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检测边是否已经存在（内部方法）
     * @param line
     * @param outerLines
     * @param innerLinesGroup
     * @returns {boolean}
     */
    static isExist(line, outerLines, innerLinesGroup) {
        if (Utils.inElements(line, outerLines, lineCompare))return true;
        for (let i of innerLinesGroup) {
            if (Utils.inElements(line, i, lineCompare))return true;
        }
        return false;
    }

    /**
     * 寻找最小半径点（内部方法）
     * @param line
     * @param Points
     * @param allLines
     * @param allPoints
     * @param TINFaces
     * @param averageNormal
     * @returns {{edge1: *, edge2: *, point: *}|boolean|boolean|*}
     */
    static findMinRadiusPointInfo(line, Points, allLines, allPoints, TINFaces,averageNormal) {
        let points = [...Points];
        let point1 = new Vector3().copy(line.start);
        let point2 = new Vector3().copy(line.end);
        let point3=new Vector3();
        let index = -1;
        let minRadius = null;
        let minCircle=null;
        let circle=new Circle();
        let curRadius = null;
        for (let i = 0; i < points.length; i++) {
            point3.copy(points[i]);
            if(GeometryUtils.checkColinear3D(point1,point2,point3))continue;
            circle.radius=GeometryUtils.triangleCircumcircle(point1, point2, point3,circle.center,circle.normal);
            curRadius=circle.radius;
            if (minRadius === null || curRadius < minRadius) {
                minRadius = curRadius;
                index = i;
                minCircle=circle.clone();
            }
        }
        if (index === -1) {
            return false;
        }
        let tempLine1 = new Line(point1, points[index]);
        let tempLine2 = new Line(point2, points[index]);
        if (TriangulateWithOutlinePoints.isIntersectWithAnyLine(new TINLine(tempLine1, averageNormal), allLines)) {
            points.splice(index, 1);
            return TriangulateWithOutlinePoints.findMinRadiusPointInfo(line, points, allLines, allPoints, TINFaces,averageNormal);
        } else if (TriangulateWithOutlinePoints.isIntersectWithAnyLine(new TINLine(tempLine2, averageNormal), allLines)) {
            points.splice(index, 1);
            return TriangulateWithOutlinePoints.findMinRadiusPointInfo(line, points, allLines, allPoints, TINFaces,averageNormal);
        } else if (averageNormal.dot(minCircle.normal)<0) {
            points.splice(index, 1);
            return TriangulateWithOutlinePoints.findMinRadiusPointInfo(line, points, allLines, allPoints, TINFaces,averageNormal);
        } else if ((()=> {
            let projectPoint1 = new Vector3().copy(point1).projectOnPlane(averageNormal);
            let projectPoint2 = new Vector3().copy(point2).projectOnPlane(averageNormal);
            let projectPoint3 = new Vector3().copy(points[index]).projectOnPlane(averageNormal);
            let tempProjectPoint = new Vector3();
            for (let i of allPoints) {
                tempProjectPoint.copy(i).projectOnPlane(averageNormal);
                if (i.equals( point1))continue;
                if (i.equals(point2))continue;
                if (i.equals( points[index]))continue;
                if (GeometryUtils.ifPointInTriangle(projectPoint1, projectPoint2, projectPoint3, tempProjectPoint))return true;
            }
            return false;
        })()) {
            points.splice(index, 1);
            return TriangulateWithOutlinePoints.findMinRadiusPointInfo(line, points, allLines, allPoints, TINFaces,averageNormal);
        }
        if ((()=> {
            let indexA = Utils.inElements(point1, allPoints,vecCompare,true);
            let indexB = Utils.inElements(point2, allPoints, vecCompare,true);
            let indexC = Utils.inElements(points[index], allPoints, vecCompare,true);
            let tempFA = new TINFace(indexA, indexB, indexC);
            return Utils.inElements(tempFA, TINFaces, faceCompare);
        })()) {
            points.splice(index, 1);
            return TriangulateWithOutlinePoints.findMinRadiusPointInfo(line, points.slice(0), allLines, allPoints, TINFaces,averageNormal);
        } else {
            return {point: points[index], edge1: tempLine1, edge2: tempLine2};
        }
    }

    /**
     * 生成原始边集（内部方法）
     * @param outer
     * @param inners
     * @returns {{allLines: *, outer: *, allPoints: *, inner: *}}
     */
    static createLines(outer, inners) {
        let tempLine = undefined;
        let aPoints = [];
        let aLines = [];
        let oLines = [];
        for (let i = 0; i < outer.length - 1; i++) {
            aPoints.push(outer[i]);
            tempLine = new Line(outer[i], outer[i + 1]);
            if (tempLine.length!== 0) {
                oLines.push(tempLine);
                aLines.push(tempLine);
            }
        }
        aPoints.push(outer[outer.length - 1]);
        tempLine = new Line(outer[outer.length - 1], outer[0]);
        if (tempLine.length!== 0) {
            oLines.push(tempLine);
            aLines.push(tempLine);
        }
        Utils.removeDuplicated(oLines,lineCompare);
        let iLinesGroup = [];
        inners.forEach(function (inner) {
            if(inner.length>0) {
                let iLines = [];
                for (let i = 0; i < inner.length - 1; i++) {
                    aPoints.push(inner[i]);
                    tempLine = new Line(inner[i], inner[i + 1]);
                    if (tempLine.length!== 0) {
                        iLines.push(tempLine);
                        aLines.push(tempLine);
                    }
                }
                aPoints.push(inner[inner.length - 1]);
                tempLine = new Line(inner[inner.length - 1], inner[0]);
                if (tempLine.length!==0) {
                    iLines.push(tempLine);
                    aLines.push(tempLine);
                }
                Utils.removeDuplicated(iLines,lineCompare);
                iLinesGroup.push(iLines);
            }
        });
        Utils.removeDuplicated(aPoints,vecCompare);
        Utils.removeDuplicated(aLines,lineCompare);
        return {outer: oLines, inner: iLinesGroup, allLines: aLines, allPoints: aPoints};
    }

    /**
     * 获取顶点集，剖分边集（内部方法）
     * @param outerPoints
     * @param innerPointsGroup
     * @param averageNormal
     * @returns {{allLines: *, innerLinesGroup: *, outerLines: *, allPoints: *}}
     */
    static createTINLines(outerPoints, innerPointsGroup,averageNormal) {
        let tempTINLine = undefined;
        let lines = TriangulateWithOutlinePoints.createLines(outerPoints, innerPointsGroup);
        let allPoints = lines.allPoints;
        let outerLines = [];
        let innerLinesGroup = [];
        let allLines = [];
        for(let i of lines.outer){
            tempTINLine = new TINLine(i, averageNormal);
            outerLines.push(tempTINLine);
        }
        lines.inner.forEach(function (iLines) {
            let tempGroup = [];
            for (let i of iLines) {
                tempTINLine = new TINLine(i, averageNormal);
                tempGroup.push(tempTINLine);
            }
            if (tempGroup.length > 0) innerLinesGroup.push(tempGroup);
        });
        for (let i of lines.allLines) {
            tempTINLine = new TINLine(i, averageNormal);
            allLines.push(tempTINLine);
        }
        return {allPoints: allPoints, allLines: allLines, outerLines: outerLines, innerLinesGroup: innerLinesGroup};
    }

    /**
     * 剖分操作
     * @param allPoints
     * @param allLines
     * @param outerLines
     * @param innerLinesGroup
     * @param averageNormal
     * @returns {[]}
     * @constructor
     */
    static TINOuter(allPoints, allLines, outerLines, innerLinesGroup,averageNormal) {
        let tempLine = undefined;
        let tempTINLine = undefined;
        let tempPointInfo = undefined;
        let tempIndex = undefined;
        let count = 0;
        let TINFaces = [];
        while (outerLines.length > 0) {
            count++;
            if (count > 1000) {
                console.warn('JORScripts.Triangulation->TINWithOutlinePoints->TINOuter encountered an error,the loop cannot be ended at time!');
                break;
            }
            for (let i = 0; i < outerLines.length; i++) {
                tempLine = outerLines[i];
                tempPointInfo = TriangulateWithOutlinePoints.findMinRadiusPointInfo(tempLine, allPoints, allLines, allPoints, TINFaces, averageNormal);
                if (tempPointInfo === false) {
                    tempLine = new Line(tempLine.end, tempLine.start);
                    tempLine = new TINLine(tempLine, averageNormal);
                    tempPointInfo = TriangulateWithOutlinePoints.findMinRadiusPointInfo(tempLine, allPoints, allLines, allPoints, TINFaces, averageNormal);
                    if (tempPointInfo === false) {
                        outerLines.splice(i, 1);
                        continue;
                    }
                }
                outerLines.splice(i, 1);
                if (!TriangulateWithOutlinePoints.isExist(tempPointInfo.edge1, outerLines, innerLinesGroup)) {
                    tempTINLine = new TINLine(tempPointInfo.edge1, averageNormal);
                    allLines.push(tempTINLine);
                    outerLines.push(tempTINLine);
                } else {
                    if (Utils.inElements(tempPointInfo.edge1, outerLines, lineCompare)) {
                        tempIndex = Utils.inElements(tempPointInfo.edge1, outerLines, lineCompare,true);
                        outerLines.splice(tempIndex, 1);
                    }
                }
                if (!TriangulateWithOutlinePoints.isExist(tempPointInfo.edge2, outerLines, innerLinesGroup)) {
                    tempTINLine = new TINLine(tempPointInfo.edge2, averageNormal);
                    allLines.push(tempTINLine);
                    outerLines.push(tempTINLine);
                } else {
                    if (Utils.inElements(tempPointInfo.edge2, outerLines, lineCompare)) {
                        tempIndex = Utils.inElements(tempPointInfo.edge2, outerLines, lineCompare,true);
                        outerLines.splice(tempIndex, 1);
                    }
                }
                let aIndex = Utils.inElements(tempLine.start, allPoints, vecCompare,true);
                let bIndex = Utils.inElements(tempLine.end, allPoints, vecCompare,true);
                let cIndex = Utils.inElements(tempPointInfo.point, allPoints, vecCompare, true);

                TINFaces.push(new TINFace(aIndex, bIndex, cIndex));
            }
        }
        return TINFaces;
    }
}

/**
 * 三角剖分
 */
export default class Triangulation {
    constructor() {
        this._type = Triangulation.type.outlinePoints;
        this._meshInfo = undefined;
    }

    /**
     * 支持的剖分类型
     * @returns {{outlinePoints: string}}
     * OUTLINE：基于轮廓点的剖分
     */
    static get type(){
        return {
            outlinePoints: 'OUTLINE',
        };
    }

    /**
     * 设置剖分类型
     * @param type
     */
    set type(type) {
        this._type = type;
    }

    /**
     * 输入剖分所需的数据并依据相应类型剖分
     * @param data 主要数据
     * @param datas 其它数据
     */
    setInfo(data, ...datas) {
        switch (this._type) {
            case Triangulation.type.outlinePoints:
                this._meshInfo = TriangulateWithOutlinePoints.TINOutlines(data, datas);
                break;
        }
    }

    /**
     * 获取剖分所得几何体（BufferGeometry）
     */
    get geometry() {
        if (this._meshInfo === undefined) {
            console.warn('Triangulation->meshInfo is undefined!');
            return;
        }
        let tempPoints = this._meshInfo.points;
        let tempFaces = this._meshInfo.faces;

        // 20210824
        // let geometry = new THREE.Geometry();
        // geometry.vertices = tempPoints;
        // for (let i = 0; i < tempFaces.length; i++) {
        //     let face = new THREE.Face3(tempFaces[i].a, tempFaces[i].b, tempFaces[i].c);
        //     geometry.faces.push(face);
        // }
        // geometry.computeFaceNormals();
        let geometry = new BufferGeometry();
        let positions=new Float32Array(tempPoints.length*3);
        for(let i=0;i<tempPoints.length;i++){
            positions[i*3]=tempPoints[i].x;
            positions[i*3+1]=tempPoints[i].y;
            positions[i*3+2]=tempPoints[i].z;
        }
        let index=[];
        for (let i = 0; i < tempFaces.length; i++) {
            index.push(tempFaces[i].a, tempFaces[i].b, tempFaces[i].c);
        }
        geometry.setIndex(index);
        geometry.setAttribute('position',new BufferAttribute(positions,3));
        geometry.computeVertexNormals();

        return geometry;
    }

    /**
     * 获取剖分结果的平均法线
     * @returns {*|Vector3}
     */
    get averageNormal() {
        return this._meshInfo.averageNormal;
    }

    /**
     * 获取剖分结果（顶点集，面集等）
     */
    get geometryInfo() {
        return this._meshInfo;
    }
}